/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

namespace baja { namespace app {

class origin_camera : public iorigin_camera
{
private:
    origin_camera(const m::matrix44& coordinateSystemMatrix) 
        : 
        _coordinateSystemMatrix(coordinateSystemMatrix), 
        _angleX(0.f), 
        _angleY(0.f), 
        _angleZ(0.f), 
        _distance(0.f) 
        {}

public:
    friend class std::_Ref_count_obj<origin_camera>;
    static std::shared_ptr<origin_camera> create(const m::matrix44& coordinateSystemMatrix)
    {
        std::shared_ptr<origin_camera> returnValue = std::make_shared<origin_camera>(coordinateSystemMatrix);
        return returnValue;
    }

    // icamera
    m::point3d position()
    {
        BAJA_THROW(std::logic_error("not yet implemented")); // ERROR:
        return m::point3d();
    }

    m::vector3d direction()
    {
        BAJA_THROW(std::logic_error("not yet implemented")); // ERROR:
        return m::vector3d();
    }

    m::vector3d up()
    {
        BAJA_THROW(std::logic_error("not yet implemented")); // ERROR:
        return m::vector3d();
    }

    m::matrix44 matrix()
    {
        return _matrix;
    }

    void lerp(
        const std::shared_ptr<icamera>& destination,
        const b::float32 p
        ) 
    {
        BAJA_THROW(std::invalid_argument("not yet implemented"));
    }

    // iorigin_camera

    void set(
        const float32 angleX,
        const float32 angleY,
        const float32 angleZ,
        const float32 distance
        )
    {
        _angleX = angleX;
        _angleY = angleY;
        _angleZ = angleZ;
        _distance = distance;

        this->updateMatrix();
    }

    void rotateDeltaXYZ(
        const float32 deltaX,
        const float32 deltaY,
        const float32 deltaZ
        )
    {
        _angleX += deltaX;
        _angleY += deltaY;
        _angleZ += deltaZ;
    
        this->updateMatrix();
    }

    void setDistance(const b::float32 distance)
    {
        _distance = distance;
        this->updateMatrix();
    }

    b::float32 distance() 
    { 
        return _distance; 
    }

    void setOffsetXY(const m::point2d& offset)
    {
        _offsetXY = offset;
        this->updateMatrix();
    }

    m::point2d offsetXY()
    {
        return _offsetXY;
    }

private:

    void updateMatrix()
    {
        m::matrix44 coordSystemMatrix;
        m::matrix44 newMatrix;

        newMatrix.setRotationXYZ(
            m::degToRad(_angleX),
            m::degToRad(_angleY),
            m::degToRad(_angleZ)
            );
        
        m::vector3d upVec;
        _coordinateSystemMatrix.getUpVector(&upVec);
        m::vector3d rightVec;
        _coordinateSystemMatrix.getRightVector(&rightVec);
        m::vector3d fwdVec;
        _coordinateSystemMatrix.getForwardVector(&fwdVec);

        rightVec.scale(_offsetXY.x);
        upVec.scale(_offsetXY.y);
        fwdVec.scale(-1.0f * _distance);

        m::vector3d tmp;
        upVec.add(rightVec, &tmp);

        m::vector3d tmp2;
        tmp.add(fwdVec, &tmp2);

        m::vector3d position;
        newMatrix.apply(tmp2, &position);

        m::point3d cameraPosition = m::point3d(position);
        newMatrix.setTranslation(cameraPosition);

        _matrix = newMatrix;
    }

private:

    m::matrix44 _matrix;
    m::matrix44 _coordinateSystemMatrix;

    b::float32 _angleX;
    b::float32 _angleY;
    b::float32 _angleZ;
    b::float32 _distance;
    m::point2d _offsetXY;
};

inline std::shared_ptr<iorigin_camera> createOriginCamera(const m::matrix44& coordinateSystemMatrix) 
{
    std::shared_ptr<origin_camera> returnValue = origin_camera::create(coordinateSystemMatrix);
    return std::static_pointer_cast<iorigin_camera>(returnValue);
}

}} // namespace baja::app
